#pragma once
#include "Util/Log.hpp"
#include <list>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unordered_map>
#include <string>
#include "room.hpp"
#include "online_manager.hpp"
#include "db.hpp"
#include <vector>

template <class T>
class match_queue
{
public:
    match_queue()
    {
    }
    ~match_queue()
    {
    }

public:
    // 入队数据,并唤醒线程(入队一次就直接唤醒一次)
    void push_back(const T &data)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _lists.push_back(data);
            _cond.notify_all(); // 唤醒该队列所有消费者线程,但其实一个队列只有一个消费者线程
        }
    }
    // 出队数据(从输出型参数带出去)
    bool pop_front(T &data)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 还需要判定队列是否为空,防止伪唤醒:
            // 当线程被唤醒来执行pop_front(),但是有其他线程(生产者线程: 队列管理者)可能执行了
            // 移除指定玩家remove()导致队列为空.
            if (_lists.empty())
            {
                return false;
            }
            data = _lists.front();
            _lists.pop_front();
            return true;
        }
        return true;
    }
    // 移除指定玩家
    void remove(const T &data)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _lists.remove(data);
        }
    }
    // 获取队列元素个数
    int size()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _lists.size();
    }
    bool empty()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _lists.empty();
    }
    // 由于队列为空而阻塞当前线程
    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }

private:
    std::list<T> _lists;           // 双向链表
    std::mutex _mutex;             // 实现线程安全
    std::condition_variable _cond; // 条件变量,实现同步,当队列玩家个数<2时,线程需要在当前条件变量下等待
};

const int rank1 = 1;
const int rank2 = 2;
const int rank3 = 3;
// 3个匹配队列的管理者
class matcher
{
public:
    matcher(RoomManager *rm, Online_Manager *om, user_table *ut)
        : _rm(rm),
          _om(om),
          _ut(ut),
          _threads(10)
    {
        // 创建不同等级的匹配队列:
        _mqueues[rank1] = std::shared_ptr<match_queue<int>>(new match_queue<int>);
        _mqueues[rank2] = std::shared_ptr<match_queue<int>>(new match_queue<int>);
        _mqueues[rank3] = std::shared_ptr<match_queue<int>>(new match_queue<int>);

        // 初始化多线程并为线程设置入口函数:
        std::thread t1(&matcher::thread_handle, this, rank1);
        std::thread t2(&matcher::thread_handle, this, rank2);
        std::thread t3(&matcher::thread_handle, this, rank3);

        // 注意: std::thread 是不可复制的，但支持​​移动语义​​（通过移动构造函数 std::thread(std::thread&&)）。
        //       移动语义允许将一个 std::thread 对象的资源所有权​​转移​​给另一个对象，而无需复制底层资源
        //   或者可以emplace_back(),但本质还是移动语义
        _threads.push_back(std::move(t1));
        _threads.push_back(std::move(t2));
        _threads.push_back(std::move(t3));

        LOG(LogLevel::INFO) << "游戏匹配模块初始化完毕";
    }
    ~matcher()
    {
    }

public:
    // 添加新玩家到匹配队列
    bool add(int uid)
    {
        // 1.先获取玩家的分数
        Json::Value user;
        bool ret1 = _ut->Select_by_id(uid, user);
        if (!ret1)
        {
            LOG(LogLevel::ERROR) << "获取用户信息失败";
            return false;
        }
        int score = user["score"].asInt();

        // 2.该用户属于哪个等级,添加到对应匹配队列当中
        if (score < 1100) // 黄金
        {
            _mqueues[rank1]->push_back(uid); // push_back()内部会唤醒对应消费者线程
        }
        else if (score >= 1100 && score < 1200) // 砖石
        {
            _mqueues[rank2]->push_back(uid);
        }
        else if (score >= 1200) // 王者
        {
            _mqueues[rank3]->push_back(uid);
        }
        return true;
    }
    // 从匹配队列移除玩家
    bool remove(int uid)
    {
        // 1.先获取玩家的分数
        Json::Value user;
        bool ret1 = _ut->Select_by_id(uid, user);
        if (!ret1)
        {
            LOG(LogLevel::ERROR) << "获取用户信息失败";
            return false;
        }
        int score = user["score"].asInt();

        // 2.该用户属于哪个等级,然后从对应匹配队列当中删除
        if (score < 1100) // 黄金: rank1
        {
            _mqueues[rank1]->remove(uid);
        }
        else if (score >= 1100 && score < 1200) // 砖石: rank2
        {
            _mqueues[rank2]->remove(uid);
        }
        else if (score >= 1200) // 王者 : rank3
        {
            _mqueues[rank3]->remove(uid);
        }
        return true;
    }

private:
    // 线程的入口函数(虽然多个线程都是执行该函数,但是不同线程处理的是不同的匹配队列！)
    void thread_handle(const int &rank)
    {
        // 获取当前消费者线程对应的阻塞队列:
        std::shared_ptr<match_queue<int>> mq = _mqueues[rank];
        while (true)
        {
            // 1. 首先判断队列人数是否>=2 , <2则阻塞等待
            // 注意: 这里需要用循环,假如用if(), 那么可能该消费者线程刚从mq的条件变量醒来,
            //      本matcher线程调用了remove将其中一个玩家移除后只剩1个玩家, 此时如果该消费者线程
            //      直接执行第二步的话,就会出现错误. 当然为了保险起见,第2步的pop_front内又判断了一次!
            while (mq->size() < 2)
            {
                mq->wait();
            }
            // 2. 获取两名玩家
            int uid1, uid2;
            bool ret = mq->pop_front(uid1); // 如果pop_front内部判断玩家个数<2,就返回false
            if (ret == false)
            {
                continue;
            }
            ret = mq->pop_front(uid2);
            if (ret == false)
            {
                // 注意: 此时第二个玩家退出匹配了,但是由于第一个玩家已经出队列,但是此时他又没有对手可以匹配
                // 所以需要将上面出队的uid1重新加入匹配队列
                this->add(uid1);
                continue;
            }

            // 3. 判断玩家是否还在线(这里简化为了直接获取连接),如果有一个掉线,则需要将另一个玩家重新入队列
            WsServer_t::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
            if (conn1.get() == nullptr) // .get() 获取对应的指针
            {
                // 玩家1掉线,需要将玩家2 重新加入匹配队列
                this->add(uid2);
                continue;
            }
            WsServer_t::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
            if (conn2.get() == nullptr) // .get() 获取对应的指针
            {
                // 玩家2掉线,需要将玩家1 重新加入匹配队列
                this->add(uid1);
                continue;
            }
            // 4. 上面没问题之后,创建房间,将这两个玩家加入到房间当中
            bool ret1 = _rm->create_room(uid1, uid2);
            if (!ret1) // 创建房间失败
            {
                this->add(uid1);
                this->add(uid2);
                continue;
            }
            // 5. 给两个玩家发送"匹配成功"的响应消息
            Json::Value resp;
            resp["optype"] = "match_success";
            resp["result"] = true;
            // 序列化:
            std::string body;
            JSON_Util::Serialize(resp, body);
            // 发送正文  (此时连接状态是: 游戏大厅的websocket长连接)
            conn1->send(body);
            conn2->send(body);
        }
    }

private:
    std::unordered_map<int, std::shared_ptr<match_queue<int>>> _mqueues; // 所有级别的匹配队列
    std::vector<std::thread> _threads;                                   // 消费者多线程
    RoomManager *_rm;                                                    // 房间管理句柄
    Online_Manager *_om;                                                 // 在线用户管理句柄
    user_table *_ut;                                                     // 用户表句柄
};